#include "utils.h"
#include "tokenizer.h"
#include <errno.h>
#include <stdlib.h>
#include <ctype.h>
#include <assert.h>



//////////////////////////////////////////////////////////////
// class CTokenizer


CTokenizer::CTokenizer()
{
    m_pInputStream = NULL;
    m_DelInputOnClose = false;

    m_Flags = 0;

    SetIsSpaceFunc(IsSpace);
}


bool CTokenizer::OpenMem(uint8_t *stream, size_t size, unsigned int flags)
{
    CMemCharStream *p = new CMemCharStream(stream, size);

    return p->Open() && Open(p, true, flags);
}


bool CTokenizer::OpenFile(const char *filename, unsigned int flags)
{
    CTextFileCharStream *p = new CTextFileCharStream(filename);

    if (p->Open())
    {
        return Open(p, true, flags);
    }
    else
    {
        delete p;
        return false;
    }
}


bool CTokenizer::Open(CCharStream *input, bool bAutoDelInput, unsigned int flags)
{
    Close();

    m_DelInputOnClose = bAutoDelInput;
    m_Flags = flags;
    m_pInputStream = NULL;

    if (input == NULL)
    {
        XTRACE("[CTokenizer::Open] Failed (input == NULL)\n");
        return false;
    }

    m_pInputStream = input;
    return true;
}


void CTokenizer::Close()
{
    if (m_pInputStream)
    {
        if (m_DelInputOnClose)
        {
            m_pInputStream->Close();
            delete m_pInputStream;
        }

        m_pInputStream = NULL;
    }
}


void CTokenizer::Reset()
{
    m_pInputStream->Reset();
}


int CTokenizer::IsSpace(int ch)
{
    return isspace(ch);
}


const CChString *CTokenizer::Next(bool bThrowEx)
{
    m_Current.token.Clear();
    m_Current.position = -1;

    if (m_pInputStream == NULL)
    {
        if (bThrowEx)
            throw CTokenizerException("<CTokenizer::Next> m_pInputStream == NULL\n");
        else
            return NULL;
    }

    CCharStream &in = *m_pInputStream;
    CChString   &out = m_Current.token;
    int          c;

    while ((c=in.GetCh()) != EOF && m_pfnIsSpace(c))
        ;

    if (c == EOF)
    {
        if (bThrowEx)
            throw CTokenizerException("<CTokenizer::Next> c == EOF\n");
        else
            return NULL;
    }

    m_Current.position = in.GetPosition();

    in.PushBack(c);

    int inquotes = 0;
    int inquotes2 = 0;

    // ciklus amig nincs EOF vagy white space (az idezojel figyelembe vetelevel)

    while ((c = in.GetCh()) != EOF && (m_pfnIsSpace(c) == 0 || ((inquotes || inquotes2) && (m_Flags & QUOTEDWORD))))
    {
        if ((m_Flags & CPP_COMMENT) != 0 && c == '/' && in.PeekCh() == '/')
        {
            while ((c=in.GetCh()) != EOF && c != '\n')
                ;

            int oldc = c;

            while ((c=in.GetCh()) != EOF && m_pfnIsSpace(c))
                ;

            if (oldc == '\n')
                in.PushBack(c);

            if (out.length())
            {
                return &m_Current.token;
            }

            m_Current.token.Clear();
            m_Current.position = in.GetPosition();
            continue;
        }

        if (c == '\\')
        {
            c = in.GetCh();

            if (c == 'n')
            {
                if (m_Flags & DONTREMOVEQUOTE)
                {
                    out += '\\';
                    out += 'n';
                }
                else
                    out += '\n';

                continue;
            }
            else if (c == '\\')
            {
                if (m_Flags & DONTREMOVEQUOTE)
                    out += '\\';

            }
            else if (c == '"')
            {
                if (m_Flags & DONTREMOVEQUOTE)
                {
                    out += '\\';
                    inquotes2 ^= 1;
                }
            }
            else if (c == EOF)
            {
                break;
            }
            else
            {
                out += '\\';
            }
        }
        else if (c == '"')
        {
            if ((m_Flags & QUOTEDWORD) != 0)
            {
                if (inquotes == false)      // ha most lepunk be az idezojelbe
                {
                    inquotes = true;

                    if ((m_Flags & DONTREMOVEQUOTE) == 0)
                        continue;
                }
                else
                {
                    inquotes = false;       // folosleges

                    if (m_Flags & DONTREMOVEQUOTE)
                        out += '\"';

                    break;
                }
            }
        }

        if (c != EOF)
            out += c;
    }


    if (m_Current.token.length() == 0)
    {
        if (bThrowEx)
            throw CTokenizerException("<CTokenizer::Next> token->length() == 0\n");
        else
            return NULL;
    }

    //XTRACE("token = '%s'\n", out.c_str());

    return &m_Current.token;
}


const CChString *CTokenizer::NextTag(const char *before, const char *after)
{
    assert(before && before[0] && after && after[0]);

    CCharStream &in = *m_pInputStream;
    CChString   &out = m_Current.token;

    out.Clear();

    for (int i=0; before[i]; i++)
    {
        int ch = toupper(in.GetCh());

        if (ch == EOF)
            throw CTokenizerException("<CTokenizer::NextTag> EOF (1)\n");

        if (ch != toupper(before[i]))
            throw CTokenizerException(CChString(ft("<CTokenizer::NextTag> '%s' not found\n", before)).c_str());
    }

    int after_position = 0;     // a zaro tag -ben vagyunk? 0 : nem, > 0: ennyiedik pozicio kovetkezik

    for (;;)
    {
        int ch = in.GetCh();

        if (ch == EOF)
        {
            //if (after[after_position+1] == '\0')
            //{
            //    out.Remove(-after_position, CChString::npos); // a zaro tag torlese
            //    return &out;
            //}

            throw CTokenizerException("<CTokenizer::NextTag> EOF (2)\n");
        }

        out += ch;

        if (after_position)
        {
            if (toupper(ch) == toupper(after[after_position]))
            {
                if (after[++after_position] == '\0')
                {
                    out.Remove(-after_position, CChString::npos); // a zaro tag torlese
                    return &out;
                }
            }
            else
                after_position = 0;
        }
        else if (toupper(ch) == toupper(after[0]))
        {
            after_position = 1;

            if (after[1] == '\0')       // 1 karakteres az 'after'?
            {
                out.RemoveLast();
                return &out;
            }
        }
    }
}


const CChString *CTokenizer::NextEqTag(const char *before, const char *after)
{
    Skip("=");
    return NextTag(before, after);
}


const CChString *CTokenizer::NextTagVar(const char *var_name, const char *before, const char *after)
{
    Skip(var_name);
    return NextEqTag(before, after);
}


void CTokenizer::Skip(const char *skip)
{
    if (strcmp(skip, Next(true)->c_str()) != 0)
        throw CTokenizerException(ft("<CTokenizer::Skip> Wrong token (received: \"%s\", expected: \"%s\").\n", m_Current.token.c_str(), skip));
}


bool CTokenizer::NextBool()
{
    Next(true);

    const char *str = m_Current.token.c_str();

    if (strcmp(str, "1") == 0 || strcmp(str, "true") == 0)
        return true;

    if (strcmp(str, "0") == 0 || strcmp(str, "false") == 0)
        return false;

    throw CTokenizerException(ft("<CTokenizer::NextBool> unacceptable token (%s)\n", str));
}


bool CTokenizer::NextEqBool()
{
    Skip("=");
    return NextBool();
}


bool CTokenizer::NextBoolVar(const char *var_name)
{
    Skip(var_name);
    return NextEqBool();
}


unsigned int CTokenizer::read_color()
{
    int c = NextInt();

    if (c < 0 || c > 255)
        XTRACE("[CTokenizer::read_color] WARNING: out of range (%d - 0..255)\n", c);

    if (c < 0)
        c = 0;

    if (c > 255)
        c = 255;

    return c;
}


unsigned CTokenizer::NextRGB()
{
    unsigned argb = 0;

    argb |= read_color() << 16;
    argb |= read_color() << 8;
    argb |= read_color();

    return argb;
}


unsigned CTokenizer::NextEqRGB()
{
    Skip("=");
    return NextRGB();
}


unsigned CTokenizer::NextRGBVar(const char *var_name)
{
    Skip(var_name);
    return NextEqRGB();
}


unsigned CTokenizer::NextARGB()
{
    unsigned argb = 0;

    argb |= read_color() << 24;
    argb |= read_color() << 16;
    argb |= read_color() << 8;
    argb |= read_color();

    return argb;
}


unsigned CTokenizer::NextEqARGB()
{
    Skip("=");
    return NextARGB();
}


unsigned CTokenizer::NextARGBVar(const char *var_name)
{
    Skip(var_name);
    return NextEqARGB();
}


int CTokenizer::NextInt()
{
    Next(true);

    errno = 0;

    char *dummy;
    long  ret = strtol(m_Current.token.c_str(), &dummy, 0);

    if (errno == ERANGE)
        throw CTokenizerException(ft("<CTokenizer::NextInt> errno == ERANGE (token = %s)\n", m_Current.token.c_str()));

    return (int)ret;
}


int CTokenizer::NextEqInt()
{
    Skip("=");
    return NextInt();
}


int CTokenizer::NextIntVar(const char *var_name)
{
    Skip(var_name);
    return NextEqInt();
}


unsigned CTokenizer::NextUInt()
{
    Next(true);

    errno = 0;

    char *dummy;
    unsigned long ret = strtoul(m_Current.token.c_str(), &dummy, 0);

    if (errno == ERANGE)
        throw CTokenizerException(ft("<CTokenizer::NextUInt> errno == ERANGE (token = %s)\n", m_Current.token.c_str()));

    return (unsigned)ret;
}


unsigned CTokenizer::NextEqUInt()
{
    Skip("=");
    return NextUInt();
}


unsigned CTokenizer::NextUIntVar(const char *var_name)
{
    Skip(var_name);
    return NextEqUInt();
}


int64_t CTokenizer::NextInt64()
{
    Next(true);


    errno = 0;

    char *dummy;
    uint64_t ret = strtoull(m_Current.token.c_str(), &dummy, 0);

    if (errno == ERANGE)
        throw CTokenizerException(ft("<CTokenizer::NextUInt64> errno == ERANGE (token = %s)\n", m_Current.token.c_str()));

    return ret;
}


int64_t CTokenizer::NextEqInt64()
{
    Skip("=");
    return NextInt64();
}


int64_t CTokenizer::NextInt64Var(const char *var_name)
{
    Skip(var_name);
    return NextEqInt64();
}


uint64_t CTokenizer::NextUInt64()
{
    Next(true);

    errno = 0;

    char *dummy;
    uint64_t ret = strtoull(m_Current.token.c_str(), &dummy, 0);

    if (errno == ERANGE)
        throw CTokenizerException(ft("<CTokenizer::NextUInt64> errno == ERANGE (token = %s)\n", m_Current.token.c_str()));

    return ret;
}


uint64_t CTokenizer::NextEqUInt64()
{
    Skip("=");
    return NextUInt64();
}


uint64_t CTokenizer::NextUInt64Var(const char *var_name)
{
    Skip(var_name);
    return NextEqUInt64();
}


float CTokenizer::NextFloat()
{
    Next(true);

    errno = 0;

    char *dummy;
    float ret = (float)strtod(m_Current.token.c_str(), &dummy);

    if (errno == ERANGE)
        throw CTokenizerException(ft("<CTokenizer::NextFloat> errno == ERANGE (token = %s)\n", m_Current.token.c_str()));

    return ret;
}


float CTokenizer::NextEqFloat()
{
    Skip("=");
    return NextFloat();
}


float CTokenizer::NextFloatVar(const char *var_name)
{
    Skip(var_name);
    return NextEqFloat();
}


char *CTokenizer::NextString(char *buffer, size_t max_buffer)
{
    Next(true);

    if (buffer)
    {

        if (max_buffer < m_Current.token.length())
            throw CTokenizerException(ft("<CTokenizer::NextString> max_buffer < token->length() (token = %s)\n", m_Current.token.c_str()));

        return strncpy(buffer, m_Current.token.c_str(), max_buffer);
    }

    return NULL;
}


char *CTokenizer::NextEqString(char *buffer, size_t max_buffer)
{
    Skip("=");
    return NextString(buffer, max_buffer);
}


char *CTokenizer::NextStringVar(const char *var_name, char *buffer, size_t max_buffer)
{
    Skip(var_name);
    return NextEqString(buffer, max_buffer);
}


const CChString *CTokenizer::NextString()
{
    Next(true);
    return &m_Current.token;
}


const CChString *CTokenizer::NextEqString()
{
    Skip("=");
    return NextString();
}


const CChString *CTokenizer::NextStringVar(const char *var_name)
{
    Skip(var_name);
    return NextEqString();
}
